/*
 * Copyright 2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package za.co.pbt.springshark;

import org.enhydra.shark.api.client.wfmc.wapi.WMConnectInfo;
import org.enhydra.shark.api.client.wfmc.wapi.WMFilter;
import org.enhydra.shark.api.client.wfmc.wapi.WMSessionHandle;
import org.enhydra.shark.api.client.wfmodel.WfProcessMgr;
import org.enhydra.shark.api.client.wfservice.*;
import org.enhydra.shark.api.common.ProcessMgrFilterBuilder;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;

/**
 * This class offers convenieces for executing Enhydra Shark operations and is
 * styled similar to other Spring Framework 'template' classes.  The method
 * </code>public Object execute(final EnhydraSharkInterfaceCallback callback)</code>
 * wraps the Enhydra Shark operation in a transaction.  This way, we don't
 * have to write the boilerplate transaction handling code contained in all
 * the Enhydra Shark examples.
 *
 * @since 1.0
 */
public class EnhydraSharkTemplate extends EnhydraSharkAccessor {

  /**
   * Cached copy of the package administration object so that we don't have to fetch it from <code>SharkInterface</code>
   * everytime.
   */
  protected PackageAdministration packageAdministration;

  /**
   * Execute an Enhydra Shark operation (using the interfaceCallback object) within
   * a transaction.  The operation throws an exception, then the transaction
   * is rolled back.
   *
   * @param interfaceCallback the action to execute
   * @return any object that is returned by the interfaceCallback
   */
  public Object execute(final EnhydraSharkInterfaceCallback interfaceCallback) {
    return transactionTemplate.execute(new TransactionCallback() {
      Object object;

      public Object doInTransaction(TransactionStatus transactionStatus) {
        SharkInterface sharkInterface = getSharkInterface();
        try {
          object = interfaceCallback.doWithEnhydraSharkInterface(sharkInterface);
        } catch (Exception e) {
          transactionStatus.setRollbackOnly();
          // TODO Introduce project-specific exceptions
          throw new RuntimeException("Could not execute Enhydra Shark operation.", e);
        }
        return object;
      }
    });
  }

  /**
   * Execute an Enhydra Shark operation (using the callback object) within
   * a transaction using the <code>SharkConnection</code> obtained from the
   * configured <code>SharkInterface</code>.  The operation throws an exception,
   * then the transaction is rolled back.
   *
   * @param connectInfo the connection details from which the session can be obtained
   * @param callback    the action to execute
   * @return any object that is returned by the callback
   */
  public Object execute(final WMConnectInfo connectInfo, final EnhydraSharkConnectionCallback callback) {
    return transactionTemplate.execute(new TransactionCallback() {
      Object object;

      public Object doInTransaction(TransactionStatus transactionStatus) {
        SharkInterface sharkInterface = getSharkInterface();
        SharkConnection sharkConnection = null;
        try {
          sharkConnection = sharkInterface.getSharkConnection();
          sharkConnection.connect(connectInfo);
          object = callback.doWithEnhydraSharkConnection(sharkInterface, sharkConnection);
        } catch (Exception e) {
          try {
            if (sharkConnection != null) {
              sharkConnection.disconnect();
            }
          } catch (Exception e1) {
            // there's not much we can do here, we are already going to rollback the transaction
          } finally {
            transactionStatus.setRollbackOnly();
          }
          // TODO Introduce project-specific exceptions
          throw new RuntimeException("Could not execute Enhydra Shark operation.", e);
        }
        return object;
      }
    });
  }

  /**
   * Execute an Enhydra Shark operation (using the callback object) within
   * a transaction using the <code>WMConnectInfo</code> to obtain a session.
   * The operation throws an exception, then the transaction is rolled back.
   *
   * @param connectInfo the enhydra shark connection for the operation
   * @param callback    the action to execute
   * @return any object that is returned by the callback
   */
  public Object execute(final WMConnectInfo connectInfo, final EnhydraSharkSessionCallback callback) {
    return transactionTemplate.execute(new TransactionCallback() {
      Object object;

      public Object doInTransaction(TransactionStatus transactionStatus) {
        SharkInterface sharkInterface = getSharkInterface();
        SharkConnection sharkConnection = null;
        try {
          sharkConnection = sharkInterface.getSharkConnection();
          sharkConnection.connect(connectInfo);
          WMSessionHandle sessionHandle = sharkConnection.getSessionHandle();
          object = callback.doWithEnhydraSharkSession(sharkInterface, sharkConnection, sessionHandle);
        } catch (Exception e) {
          try {
            if (sharkConnection != null) {
              sharkConnection.disconnect();
            }
          } catch (Exception e1) {
            // there's not much we can do here, we are already going to rollback the transaction
          } finally {
            transactionStatus.setRollbackOnly();
          }
          // TODO Introduce project-specific exceptions
          throw new RuntimeException("Could not execute Enhydra Shark operation.", e);
        }
        return object;
      }
    });
  }

  /**
   * @return Returns the Package Administrator for Enhydra Shark
   * @throws Exception the exception from the enhydra shark operation
   */
  public PackageAdministration getPackageAdministration() throws Exception {
    if (packageAdministration == null) {
      packageAdministration = (PackageAdministration) execute(new EnhydraSharkInterfaceCallback() {
        public Object doWithEnhydraSharkInterface(SharkInterface sharkInterface) throws Exception {
          return sharkInterface.getPackageAdministration();
        }
      });
    }
    return packageAdministration;
  }

  /**
   * Open a package
   *
   * @param connectInfo The connection information to use for Enhydra Shark
   * @param packageName The name of the package (usually an absolute file path)
   * @return the entity containing the package information
   */
  public WMEntity openPackage(final WMConnectInfo connectInfo, final String packageName) {
    return (WMEntity) execute(connectInfo, new EnhydraSharkSessionCallback() {
      public Object doWithEnhydraSharkSession(SharkInterface sharkInterface, SharkConnection sharkConnection, WMSessionHandle sessionHandle) throws Exception {
        PackageAdministration packageAdministration = getPackageAdministration();
        if (!packageAdministration.isPackageOpened(sessionHandle, packageName)) {
          return packageAdministration.openPackage(sessionHandle, packageName);
        } else {
          // TODO Provide support for package versions
          return packageAdministration.getPackageEntity(sessionHandle, packageName, "");
        }
      }
    });
  }

  /**
   * Close a package given the entity containing the package information
   *
   * @param connectInfo   The connection information to use for Enhydra Shark
   * @param packageEntity the entity containing the package details
   * @return the entity containing the package information
   */
  public WMEntity closePackage(WMConnectInfo connectInfo, WMEntity packageEntity) {
    return closePackage(connectInfo, packageEntity.getPkgId(), packageEntity.getPkgVer());
  }

  /**
   * Close a package given the package identifier and version
   *
   * @param connectInfo    the connection information to use
   * @param packageId      the package identifier
   * @param packageVersion the version of the package
   * @return the entity containing the package information
   */
  public WMEntity closePackage(WMConnectInfo connectInfo, final String packageId, final String packageVersion) {
    return (WMEntity) execute(connectInfo, new EnhydraSharkSessionCallback() {
      public Object doWithEnhydraSharkSession(SharkInterface sharkInterface, SharkConnection sharkConnection, WMSessionHandle sessionHandle) throws Exception {
        return getPackageAdministration().closePackage(sessionHandle, packageId, packageVersion);
      }
    });
  }

  /**
   * Get all process managers for a package given the entity containing package information
   *
   * @param connectInfo   the connection information to use
   * @param packageEntity the entity containing the package details
   * @return array of process managers
   */
  public WfProcessMgr[] getProcessMgrsForPackage(WMConnectInfo connectInfo, WMEntity packageEntity) {
    return getProcessMgrsForPackage(connectInfo, packageEntity.getPkgId(), packageEntity.getPkgVer());
  }

  /**
   * Get all process managers for a package given a package identifier and package version
   *
   * @param connectInfo    the connection information to use
   * @param packageId      the package identifier
   * @param packageVersion the version of the package
   * @return array of process managers
   */
  public WfProcessMgr[] getProcessMgrsForPackage(final WMConnectInfo connectInfo, final String packageId, final String packageVersion) {
    return (WfProcessMgr[]) execute(connectInfo, new EnhydraSharkSessionCallback() {
      public Object doWithEnhydraSharkSession(SharkInterface sharkInterface, SharkConnection sharkConnection, WMSessionHandle sessionHandle) throws Exception {
        ProcessMgrFilterBuilder filterBuilder = sharkInterface.getProcessMgrFilterBuilder();
        WMFilter filter = filterBuilder.createEmptyFilter(sessionHandle);
        filter = filterBuilder.and(sessionHandle, filter, filterBuilder.addPackageIdEquals(sessionHandle, packageId));
        filter = filterBuilder.and(sessionHandle, filter, filterBuilder.addVersionEquals(sessionHandle, packageVersion));

        WfProcessMgrIterator iterator = sharkConnection.get_iterator_processmgr();
        iterator.set_query_expression(filterBuilder.toIteratorExpression(sessionHandle, filter));
        return iterator.get_next_n_sequence(0);
      }
    });
  }
}
